iOS 16でゼロ幅スペースが原因でURL変換に失敗する

iOS 16でゼロ幅スペースが原因でURL変換に失敗する

Clock Icon2024.10.22

最近、iOSアプリの開発中にJSONデコードが失敗する問題が発生した。原因はURLにゼロ幅スペース(U+200B)が含まれていたためだ。iOS 17以降のデバイスではこの問題が発生しておらず、発見が遅れてしまった。本記事では、この問題の詳細とその解決策について説明する。

検証環境

  • Xcode 16 (16A242d)
  • iOS 16.4 (URLの変換に失敗)
  • iOS 17.0 (URLの変換に成功)
  • iOS 18.0 (URLの変換に成功)

問題の再現

以下のコードで問題を再現できる。

import SwiftUI

struct Person: Codable {
    let name: String
    let url: URL
}

func action() {
    let zwspJsonString = """
    {
      "name": "Wada​ Kenji",
      "url": "http://example.com/index.html​"
    }
    """

    do {
        let jsonData = zwspJsonString.data(using: .utf8)!
        let person = try JSONDecoder().decode(Person.self, from: jsonData)
        print("Name: \(person.name)")
    } catch {
        print("Decoding failed: \(error)")
    }
}

問題の詳細

JSONデコード自体はゼロ幅スペースの影響を受けないため、nameフィールドにゼロ幅スペースが含まれていても正常にデコードされる。

しかし、zwspJsonStringを使うと、urlフィールドのデコードに失敗し、以下のようなエラーが発生する。

Decoding failed: dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "url", intValue: nil)], debugDescription: "Invalid URL string.", underlyingError: nil))

zwspJsonStringは見た目ではわからないが、「Wada」と「Kenji」の間、URLの最後にゼロ幅スペースが含まれている。この特殊文字は、テキストエディタや専用のViewerで確認できる。

スクリーンショット 2024-10-22 12.28.43

iOS 17以上では問題なくデコードできるが、iOS 16ではURL(string:)メソッドがゼロ幅スペースを含む文字列の場合にnilを返してしまうためデコードに失敗する。

解決策

以下の2つの解決策が考えられる。

1. アプリ側でデータクリーニングをおこなう

URLを生成する前にゼロ幅スペースを除去することで解決できる。

import Foundation

extension String {
    func removeZeroWidthSpaces() -> String {
        return self.replacingOccurrences(of: "\u{200B}", with: "")
    }
}

struct Person: Codable {
    let name: String
    let url: URL

    enum CodingKeys: String, CodingKey {
        case name, url
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)

        let urlString = try container.decode(String.self, forKey: .url)
        let cleanedUrlString = urlString.removeZeroWidthSpaces()

        guard let url = URL(string: cleanedUrlString) else {
            throw DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: container.codingPath + [CodingKeys.url],
                    debugDescription: "Invalid URL string."
                )
            )
        }
        self.url = url
    }
}

Personのイニシャライザ内でremoveZeroWidthSpaces()メソッドを使ってゼロ幅スペースを除去してからURLを生成している。この方法で修正は可能だが、対応文字列の抜けやパフォーマンスへの影響を考えると、アプリ(クライアント側)で対応するのはおすすめできない。

2. JSONの提供者への依頼

JSONデータの提供者に、URLフィールドにゼロ幅スペースを含めないよう依頼する。こちらの方が根本的な解決策になるだろう。

まとめ

アプリ側でゼロ幅スペースを除去することで問題を解決できるが、ストアへの申請やユーザーへの浸透に時間がかかるため、即時解決とは言えない。また、変換漏れやパフォーマンスの問題も考慮する必要があり、アプリ側での対応には限界がある。

一方で、JSONデータの提供者に修正を依頼できれば、それが最も迅速で根本的な解決策となるだろう。

関連記事

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.